package com.dily.scaffold.common.utils;

import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import org.docx4j.Docx4J;
import org.docx4j.TraversalUtil;
import org.docx4j.XmlUtils;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.ClassFinder;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.jaxb.Context;
import org.docx4j.model.datastorage.migration.VariablePrepare;
import org.docx4j.model.table.TblFactory;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.*;
import org.springframework.core.io.ClassPathResource;

import javax.xml.bind.JAXBElement;
import java.io.*;
import java.util.*;

/**
 * Time:   2021/12/20 13:22
 * Author: Dily
 * Remark:
 * Word文档工具
 * maven 以下依赖一个不能少
 * <dependency>
 * <groupId>com.itextpdf</groupId>
 * <artifactId>itextpdf</artifactId>
 * <version>5.5.13.2</version>
 * </dependency>
 * <dependency>
 * <groupId>freemarker</groupId>
 * <artifactId>freemarker</artifactId>
 * <version>2.3.8</version>
 * </dependency>
 * <dependency>
 * <groupId>org.docx4j</groupId>
 * <artifactId>docx4j</artifactId>
 * <version>6.1.2</version>
 * </dependency>
 * <dependency>
 * <groupId>org.docx4j</groupId>
 * <artifactId>docx4j-export-fo</artifactId>
 * <version>8.1.7</version>
 * </dependency>
 * </dependency>
 * <dependency>
 * <groupId>javax.xml.bind</groupId>
 * <artifactId>jaxb-api</artifactId>
 * <version>2.3.1</version>
 * </dependency>
 * <dependency>
 * <groupId>javax.activation</groupId>
 * <artifactId>activation</artifactId>
 * <version>1.1</version>
 * </dependency>
 * <dependency>
 * <groupId>org.glassfish.jaxb</groupId>
 * <artifactId>jaxb-runtime</artifactId>
 * <version>2.3.5</version>
 * </dependency>
 * <!-- 条形码 -->
 * <dependency>
 * <groupId>net.sf.barcode4j</groupId>
 * <artifactId>barcode4j-light</artifactId>
 * <version>2.0</version>
 */
public class Docx4jUtils {

    public static WordOption getWordOption() {
        return new WordOption();
    }

    /**
     * 获取文件中所有内容
     *
     * @param context  word 正文： getWordMLPackage().getMainDocumentPart().getContent()
     * @param toSearch 搜索的类型
     * @return 符合搜索类型的所有对象
     */
    public static List<Object> getAllElementFromObject(List<Object> context, Class<?> toSearch) {
        List<Object> result = new ArrayList<>();
        for (Object obj : context) {
            if (obj instanceof JAXBElement)
                obj = ((JAXBElement<?>) obj).getValue();
            if (obj.getClass().equals(toSearch))
                result.add(obj);
            else if (obj instanceof ContentAccessor) {
                List<Object> children = ((ContentAccessor) obj).getContent();
                getAllElementFromObject(children, toSearch);
            }
        }
        return result;
    }

    /**
     * word 转 pdf
     *
     * @param wordPath word文档地址
     * @return pdf地址
     */
    public static String Docx2Pdf(String wordPath) {
        OutputStream os = null;
        InputStream is = null;
        //输出pdf文件路径和名称
        String pdfNoMarkPath = wordPath.substring(0, wordPath.indexOf('.')) + ".pdf";
        try {
            is = new FileInputStream(wordPath);
            WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
            Mapper fontMapper = new IdentityPlusMapper();
            fontMapper.put("等线", PhysicalFonts.get("SimSun"));
            fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
            fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
            fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
            fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
            fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
            fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
            fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
            fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
            fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
            fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
            fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
            fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
            fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
            fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
            fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
            fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
            fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
            fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
            fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
            fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
            fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
            fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
            fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
            PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));            //解决宋体（正文）和宋体（标题）的乱码问题
            PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
            fontMapper.put("SimSun", PhysicalFonts.get("SimSun"));             //宋体&新宋体
            mlPackage.setFontMapper(fontMapper);

            os = new FileOutputStream(pdfNoMarkPath);

            //docx4j  docx转pdf
            FOSettings foSettings = Docx4J.createFOSettings();
            foSettings.setWmlPackage(mlPackage);
            Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);

            is.close();//关闭输入流
            os.close();//关闭输出流

            return pdfNoMarkPath;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if (is != null) {
                    is.close();
                }
                if (os != null) {
                    os.close();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } finally {
            File file = new File(wordPath);
            if (file.isFile() && file.exists()) {
                file.delete();
            }
        }
        return "";
    }

    /**
     * 添加水印图片
     *
     * @param inPdfPath 无水印pdf路径
     * @param markPath  水印图片地址
     * @return 生成的带水印的pdf路径
     */
    public static String addTextMark(String inPdfPath, String markPath) {
        PdfStamper stamp = null;
        PdfReader reader = null;
        try {
            //输出pdf带水印文件路径和名称
            String outPdfMarkPath = inPdfPath.substring(0, inPdfPath.indexOf('.')) + "水印.pdf";

            //添加水印
            reader = new PdfReader(inPdfPath, "PDF".getBytes());
            stamp = new PdfStamper(reader, new FileOutputStream(outPdfMarkPath));
            PdfContentByte under;
            int pageSize = reader.getNumberOfPages();// 原pdf文件的总页数
            //水印图片
            Image image;
            if (markPath.contains(":"))
                image = Image.getInstance(markPath);
            else {
                ClassPathResource resource = new ClassPathResource(markPath);
                image = Image.getInstance(resource.getFile().getPath());
            }
            for (int i = 1; i <= pageSize; i++) {
                under = stamp.getOverContent(i);// 水印在之前文本下
                for (int j = 0; j < 4; j++) {
                    image.setAbsolutePosition(0, j * 250 + 100);//水印位置
                    under.addImage(image);
                    image.setAbsolutePosition(200, j * 250 + 100);//水印位置
                    under.addImage(image);
                    image.setAbsolutePosition(400, j * 250 + 100);//水印位置
                    under.addImage(image);
                }
            }
            stamp.close();// 关闭
            reader.close();//关闭

            return outPdfMarkPath;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if (stamp != null) {
                    stamp.close();
                }
                if (reader != null) {
                    reader.close();//关闭
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } finally {
            //删除生成的无水印pdf
            File file = new File(inPdfPath);
            if (file.exists() && file.isFile()) {
                file.delete();
            }
        }
        return "";
    }

    /**
     * word 单步操作类
     */
    public static class WordOption {

        private static WordprocessingMLPackage wordMLPackage;

        public WordprocessingMLPackage getWordMLPackage() {
            return wordMLPackage;
        }

        /**
         * 创建一个空白 Docx 文档
         */
        public WordOption createDocx() throws Docx4JException {
            wordMLPackage = WordprocessingMLPackage.createPackage();
            return this;
        }

        /**
         * 加载 Docx 文件
         *
         * @param filePath 文件地址
         * @return WordProcessingMLPackage操作包
         */
        public WordOption loadDocx(String filePath) throws Docx4JException {
            wordMLPackage = WordprocessingMLPackage.load(new File(filePath));
            return this;
        }

        /**
         * 添加带样式的文本/段落
         *
         * @param styled  样式
         * @param conText 文本
         */
        public WordOption addContext(String styled, String conText) {
            wordMLPackage.getMainDocumentPart().addStyledParagraphOfText(styled, conText);
            return this;
        }

        /**
         * 添加文本/段落
         *
         * @param conText 正文
         */
        public WordOption addContext(String conText) {
            wordMLPackage.getMainDocumentPart().addParagraphOfText(conText);
            return this;
        }

        /**
         * 添加图片
         *
         * @param imagePath 图片地址
         */
        public WordOption addImage(String imagePath) throws Exception {
            BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, new File(imagePath));
            Inline inline = imagePart.createImageInline("Filename hint", "Alternative text", 1, 2, false);
            ObjectFactory factory = new ObjectFactory();
            P paragraph = factory.createP();
            R run = factory.createR();
            paragraph.getContent().add(run);
            Drawing drawing = factory.createDrawing();
            run.getContent().add(drawing);
            drawing.getAnchorOrInline().add(inline);
            wordMLPackage.getMainDocumentPart().addObject(paragraph);
            return this;
        }

        /**
         * 添加空表格
         *
         * @param row 行数
         * @param col 列数
         */
        public WordOption addTable(int row, int col) {
            Tbl table = TblFactory.createTable(row, col, 20000 / col);
            wordMLPackage.getMainDocumentPart().addObject(table);
            return this;
        }

        /**
         * 添加带数据表格, 数据必须是整齐的
         * 表头为数据的 key, 表头在第一行
         *
         * @param list 数据
         */
        public WordOption addTableWithDataAndTopHeader(List<Map<String, String>> list) {
            Set<String> keySet = new HashSet<>(list.get(0).keySet());
            ObjectFactory factory = Context.getWmlObjectFactory();
            Tbl table = TblFactory.createTable(0, 0, 20000 / list.get(0).size());

            // 表头
            Tr tableHeader = factory.createTr();
            keySet.forEach(e -> {
                Tc tableCell = factory.createTc();
                tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e));
                tableHeader.getContent().add(tableCell);
            });
            table.getContent().add(tableHeader);

            // 数据
            list.forEach(e -> {
                Tr tableRow = factory.createTr();
                keySet.forEach(item -> {
                    Tc tableCell = factory.createTc();
                    tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e.get(item)));
                    tableRow.getContent().add(tableCell);
                });
                table.getContent().add(tableRow);
            });
            wordMLPackage.getMainDocumentPart().addObject(table);
            return this;
        }

        /**
         * 添加带数据表格, 数据必须是整齐的
         * 表头为数据的 key, 表头在第一列
         *
         * @param list 数据
         */
        public WordOption addTableWithDataAndLeftHeader(List<Map<String, String>> list) {
            Set<String> keySet = new HashSet<>(list.get(0).keySet());
            ObjectFactory factory = Context.getWmlObjectFactory();
            Tbl table = TblFactory.createTable(0, 0, 20000 / list.get(0).size());
            keySet.forEach(e -> {
                Tr tableRow = factory.createTr();
                Tc tableHeader = factory.createTc();
                tableHeader.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e));
                tableRow.getContent().add(tableHeader);
                list.forEach(item -> {
                    Tc tableCell = factory.createTc();
                    tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(item.get(e)));
                    tableRow.getContent().add(tableCell);
                });
                table.getContent().add(tableRow);
            });
            wordMLPackage.getMainDocumentPart().addObject(table);
            return this;
        }

        /**
         * 替换模板Docx中数据和表格数据动态添加
         * 必须存在占位符
         *
         * @param data               全局替换属性Map
         * @param tableDataList      列表属性
         * @param tableTemplateIndex 表格模版位置
         * @param tableIndex         表格位置
         * @throws Exception 异常
         */
        public WordOption replaceData(Map<String, String> data, List<Map<String, Object>> tableDataList, int tableTemplateIndex, int tableIndex) throws Exception {
            // 构造循环列表的数据
            ClassFinder find = new ClassFinder(Tbl.class);

            new TraversalUtil(wordMLPackage.getMainDocumentPart().getContent(), find);

            // 获取第二个表格属性
            if (find.results.size() > 0) {
                Tbl table = (Tbl) find.results.get(tableIndex);

                // 第二行约定为模板
                Tr dynamicTr = (Tr) table.getContent().get(tableTemplateIndex);

                // 获取模板行的xml数据
                String dynamicTrXml = XmlUtils.marshaltoString(dynamicTr);

                // 循环填充模板表格行数据
                int addIndex = tableTemplateIndex;
                for (Map<String, Object> dataMap : tableDataList) {

                    addIndex++;

                    Tr newTr = (Tr) XmlUtils.unmarshallFromTemplate(dynamicTrXml, dataMap);

                    table.getContent().add(addIndex, newTr);

                }

                // 删除模板行的占位行
                table.getContent().remove(tableTemplateIndex);
            }

            // 设置全局的变量替换
            wordMLPackage.getMainDocumentPart().variableReplace(data);
            return this;
        }

        /**
         * 加载模板并替换数据
         * 必须存在占位符
         *
         * @param data 数据属性map
         * @return 输出文件路径
         * @throws Exception 异常
         */
        public WordOption replaceData(Map<String, String> data) throws Exception {
            MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
            VariablePrepare.prepare(wordMLPackage);

            // 替换属性
            documentPart.variableReplace(data);

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            wordMLPackage.save(outputStream);
            return this;
        }

        public void save(String path) throws Docx4JException {
            wordMLPackage.save(new File(path));
        }

        public void save(OutputStream outputStream) throws Docx4JException {
            wordMLPackage.save(outputStream);
        }
    }

}

